package day32.Stream;

import java.util.*;
import java.util.stream.*;

/**
 * Stream API常用功能测试类
 * 
 * Stream是Java 8引入的一个重要特性，用于处理集合数据。
 * 它提供了一种高效且易于使用的处理数据的方式。
 * 
 * 主要特点：
 * 1. 不是数据结构，不存储数据，只是对数据源的操作
 * 2. 不会改变原数据源
 * 3. 延迟执行，只有在遇到终止操作时才会执行
 * 4. 可以并行处理提高性能
 * 
 * Stream操作的三个步骤：
 * 1. 创建Stream：从数据源（如集合、数组）获取Stream
 * 2. 中间操作：对Stream进行一系列操作（如过滤、映射等），每次操作返回一个新的Stream
 * 3. 终止操作：产生结果（如收集、计数等），执行完后Stream就不能再使用了
 */
public class StreamTest {
    
    /**
     * 程序入口方法
     * 调用各个测试方法，演示Stream API的各种功能
     * @param args 命令行参数
     */
    public static void main(String[] args) {
        // 测试Stream的创建方式
        testStreamCreation();
        
        // 测试中间操作
        testIntermediateOperations();
        
        // 测试终端操作
        testTerminalOperations();
        
        // 测试收集器
        testCollectors();
        
        // 测试并行流
        testParallelStream();
    }
    
    /**
     * 测试Stream的创建方式
     * Stream可以通过多种方式创建，包括从集合、数组、值序列等创建
     * 这些创建方式为我们提供了灵活的数据处理入口
     */
    public static void testStreamCreation() {
        System.out.println("=== Stream创建方式测试 ===");
        
        // 1. 从集合创建Stream
        // 使用Arrays.asList()创建一个包含"a", "b", "c", "d"四个字符串的List集合
        // Arrays.asList()方法返回一个固定大小的List，基于数组实现
        List<String> list = Arrays.asList("a", "b", "c", "d");
        // 调用List的stream()方法创建Stream对象
        // stream()方法返回一个顺序流，可以对集合中的元素进行各种操作
        // 顺序流意味着元素会按照集合中的顺序依次处理
        Stream<String> stream1 = list.stream();
        // 调用count()方法统计Stream中的元素个数，这是一个终端操作，会触发Stream的执行
        // count()方法返回long类型，表示Stream中元素的总数
        System.out.println("1. 从集合创建Stream: " + stream1.count());
        
        // 2. 从数组创建Stream
        // 定义一个包含"a", "b", "c", "d"四个字符串的数组
        // 数组是Java中最基础的数据结构之一
        String[] arr = {"a", "b", "c", "d"};
        // 使用Arrays.stream()方法从数组创建Stream对象
        // 这种方式可以直接将数组转换为Stream，便于后续处理
        // Arrays.stream()是处理数组的专用方法
        Stream<String> stream2 = Arrays.stream(arr);
        // 统计Stream中的元素个数
        // count()是终止操作，会触发Stream管道的执行并返回结果
        System.out.println("2. 从数组创建Stream: " + stream2.count());
        
        // 3. 使用Stream.of()创建Stream
        // 使用Stream.of()静态方法直接从一组值创建Stream对象
        // 这种方式适用于直接从已知的几个值创建Stream
        // of()方法可以接受任意数量的参数
        Stream<String> stream3 = Stream.of("a", "b", "c", "d");
        // 统计Stream中的元素个数
        // 每个Stream只能使用一次，使用后就关闭了
        System.out.println("3. 使用Stream.of()创建Stream: " + stream3.count());
        
        // 4. 创建无限流
        // 使用Stream.iterate()方法创建无限流
        // 第一个参数0是种子值，第二个参数n -> n + 2是一个函数，表示每次迭代在前一个元素基础上加2
        // 这样会生成0, 2, 4, 6, 8...这样的无限序列
        // iterate方法的第一个参数是初始值，第二个参数是迭代函数
        Stream<Integer> stream4 = Stream.iterate(0, n -> n + 2);
        // 由于是无限流，必须使用limit()方法限制元素个数，这里限制为前5个元素
        // limit()是一个中间操作，限制Stream的大小
        // collect(Collectors.toList())将Stream中的元素收集到List集合中
        // Collectors.toList()是一个收集器，用于将Stream元素收集到List中
        System.out.println("4. 无限流前5个元素: " + stream4.limit(5).collect(Collectors.toList()));
        
        System.out.println();
    }
    
    /**
     * 测试中间操作
     * 中间操作会返回一个新的Stream，可以链式调用
     * 中间操作不会立即执行，只有遇到终端操作时才会真正执行（延迟执行）
     * 这种延迟执行的特性使得Stream可以进行优化，例如短路操作
     */
    public static void testIntermediateOperations() {
        System.out.println("=== 中间操作测试 ===");
        
        // 创建一个包含重复元素的整数列表，用于演示各种中间操作
        // 这个列表包含了一些重复的数字，便于演示去重等操作
        List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5, 6, 1, 9);
        
        // filter - 过滤操作
        // filter()方法接收一个Predicate（谓词）函数作为参数，用于过滤元素
        // Predicate是一个函数式接口，表示一个布尔值函数
        // x -> x > 3是一个Lambda表达式，表示过滤出所有大于3的元素
        // Lambda表达式是Java 8引入的特性，简化了匿名内部类的写法
        // collect(Collectors.toList())将过滤后的结果收集到List集合中
        List<Integer> filtered = numbers.stream()
                .filter(x -> x > 3)
                .collect(Collectors.toList());
        // 输出过滤结果：[7, 5, 6, 9]
        // 只有大于3的元素被保留下来
        System.out.println("1. 过滤大于3的元素: " + filtered);
        
        // distinct - 去重操作
        // distinct()方法用于去除Stream中的重复元素，基于元素的equals()方法判断是否重复
        // 该操作会保留每个元素的第一个出现位置
        // distinct()是基于对象的hashCode()和equals()方法来判断是否重复的
        List<Integer> distinct = numbers.stream()
                .distinct()
                .collect(Collectors.toList());
        // 输出去重结果：[3, 2, 7, 5, 6, 1, 9]
        // 重复的元素（如第二个和第三个3，第二个2）被移除
        System.out.println("2. 去重后: " + distinct);
        
        // sorted - 排序操作
        // sorted()方法用于对Stream中的元素进行排序
        // 如果元素类型实现了Comparable接口（如Integer），则可以直接调用sorted()
        // 排序默认是升序排列
        // Comparable接口定义了自然排序规则
        List<Integer> sorted = numbers.stream()
                .sorted()
                .collect(Collectors.toList());
        // 输出排序结果：[1, 2, 2, 3, 3, 3, 5, 6, 7, 9]
        // 元素按照自然顺序（从小到大）排列
        System.out.println("3. 排序后: " + sorted);
        
        // map - 映射操作
        // map()方法用于将Stream中的每个元素转换为另一个元素
        // Function接口表示一个函数，接收一个参数并产生一个结果
        // x -> x * x是一个Lambda表达式，表示将每个元素转换为其平方值
        // 这种操作常用于数据转换场景
        List<Integer> squares = numbers.stream()
                .map(x -> x * x)
                .collect(Collectors.toList());
        // 输出映射结果：[9, 4, 4, 9, 49, 9, 25, 36, 1, 81]
        // 每个元素都被转换为其平方值
        System.out.println("4. 每个元素的平方: " + squares);
        
        // limit - 限制元素个数
        // limit()方法用于截取Stream中的前n个元素
        // 这对于只需要处理部分数据的场景很有用
        // limit()是一个短路操作，可以提高性能
        List<Integer> limited = numbers.stream()
                .limit(5)
                .collect(Collectors.toList());
        // 输出限制结果：[3, 2, 2, 3, 7]
        // 只保留前5个元素
        System.out.println("5. 前5个元素: " + limited);
        
        // skip - 跳过元素
        // skip()方法用于跳过Stream中的前n个元素
        // 这对于分页或跳过不需要处理的数据很有用
        // skip()返回一个跳过前n个元素的Stream，如果元素不足则返回空Stream
        List<Integer> skipped = numbers.stream()
                .skip(5)
                .collect(Collectors.toList());
        // 输出跳过结果：[3, 5, 6, 1, 9]
        // 跳过了前5个元素，保留剩余的元素
        System.out.println("6. 跳过前5个元素: " + skipped);
        
        System.out.println();
    }
    
    /**
     * 测试终端操作
     * 终端操作会消耗Stream，产生结果或副作用
     * 终端操作触发Stream的执行，执行完后Stream就不能再使用了
     * 终端操作是Stream管道的终点，没有返回Stream
     */
    public static void testTerminalOperations() {
        System.out.println("=== 终端操作测试 ===");
        
        // 创建一个包含1到10的整数列表
        // 这是一个有序的、无重复的整数序列
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        
        // forEach - 遍历操作
        // forEach()方法用于遍历Stream中的每个元素
        // Consumer接口表示一个接受单个输入参数且不返回结果的操作
        // x -> System.out.print(x + " ")是一个Lambda表达式，表示对每个元素执行打印操作
        // 这是一个终端操作，会触发Stream的执行
        System.out.print("1. 遍历输出: ");
        numbers.stream().forEach(x -> System.out.print(x + " "));
        // 打印换行符
        System.out.println();
        
        // count - 计数操作
        // count()方法用于统计Stream中元素的个数
        // 返回值是long类型，可以处理很大的数据集
        long count = numbers.stream().count();
        // 输出元素个数：10
        System.out.println("2. 元素个数: " + count);
        
        // max/min - 最大最小值操作
        // max()方法用于找出Stream中的最大元素
        // 需要传入一个Comparator比较器，Integer::compareTo是Integer类的自然排序比较器
        // 方法引用是Lambda表达式的简化写法，格式为类名::方法名
        // 返回值是Optional类型，因为Stream可能为空
        Optional<Integer> max = numbers.stream().max(Integer::compareTo);
        // min()方法用于找出Stream中的最小元素
        Optional<Integer> min = numbers.stream().min(Integer::compareTo);
        // 使用Optional的orElse()方法获取值，如果Optional为空则返回默认值0
        // Optional是Java 8引入的容器类，用于避免空指针异常
        System.out.println("3. 最大值: " + max.orElse(0) + ", 最小值: " + min.orElse(0));
        
        // reduce - 归约操作
        // reduce()方法用于将Stream中的元素进行归约操作（如求和、求积等）
        // BinaryOperator接口表示一个作用于两个同类型操作数并产生同类型结果的操作
        // (x, y) -> x + y是一个Lambda表达式，表示将两个元素相加
        // 这会将所有元素累加起来
        Optional<Integer> sum = numbers.stream().reduce((x, y) -> x + y);
        // 输出所有元素的和：55
        System.out.println("4. 所有元素求和: " + sum.orElse(0));
        
        // anyMatch/allMatch/noneMatch - 匹配检查操作
        // anyMatch()方法检查Stream中是否至少有一个元素满足条件
        // Predicate接口表示一个布尔值函数
        // x -> x > 5是一个Lambda表达式，表示检查元素是否大于5
        boolean anyMatch = numbers.stream().anyMatch(x -> x > 5);
        // allMatch()方法检查Stream中是否所有元素都满足条件
        // x -> x > 0表示检查所有元素是否都大于0
        boolean allMatch = numbers.stream().allMatch(x -> x > 0);
        // noneMatch()方法检查Stream中是否没有元素满足条件
        // x -> x < 0表示检查是否没有元素小于0
        boolean noneMatch = numbers.stream().noneMatch(x -> x < 0);
        // 输出匹配检查结果
        System.out.println("5. 是否有元素大于5: " + anyMatch);
        System.out.println("   是否所有元素都大于0: " + allMatch);
        System.out.println("   是否没有元素小于0: " + noneMatch);
        
        System.out.println();
    }
    
    /**
     * 测试收集器
     * Collectors类提供了许多预定义的收集器，用于将Stream中的元素收集到不同的数据结构中
     * 收集器是Stream处理结果的重要方式，可以将Stream转换为各种集合类型
     */
    public static void testCollectors() {
        System.out.println("=== 收集器测试 ===");
        
        // 创建一个包含1到10的整数列表
        // 这是一个有序的、无重复的整数序列
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        
        // toList - 收集到List
        // filter(x -> x % 2 == 0)过滤出所有偶数
        // collect(Collectors.toList())将过滤结果收集到List集合中
        // List保持元素的插入顺序，允许重复元素
        List<Integer> list = numbers.stream()
                .filter(x -> x % 2 == 0)
                .collect(Collectors.toList());
        // 输出偶数列表：[2, 4, 6, 8, 10]
        System.out.println("1. 偶数列表: " + list);
        
        // toSet - 收集到Set
        // filter(x -> x > 5)过滤出所有大于5的元素
        // collect(Collectors.toSet())将过滤结果收集到Set集合中
        // Set集合会自动去重，且不保证元素顺序
        // HashSet是Set的主要实现类
        Set<Integer> set = numbers.stream()
                .filter(x -> x > 5)
                .collect(Collectors.toSet());
        // 输出大于5的数集合：[6, 7, 8, 9, 10]
        System.out.println("2. 大于5的数集合: " + set);
        
        // joining - 字符串连接
        // 创建一个包含"Java", "Stream", "API"三个字符串的列表
        // 这些是Java技术的关键字
        List<String> words = Arrays.asList("Java", "Stream", "API");
        // collect(Collectors.joining(", "))将所有字符串用", "连接起来
        // joining()方法专门用于字符串连接操作
        // Collector是收集器的接口，定义了收集操作的规则
        String joined = words.stream().collect(Collectors.joining(", "));
        // 输出连接结果："Java, Stream, API"
        System.out.println("3. 字符串连接: " + joined);
        
        // groupingBy - 分组操作
        // collect(Collectors.groupingBy(...))根据指定条件对元素进行分组
        // Function接口表示一个函数，接收一个参数并产生一个结果
        // x -> x % 2 == 0 ? "偶数" : "奇数"是一个Lambda表达式
        // 表示根据元素是否为偶数将其分为"偶数"和"奇数"两组
        // 结果是一个Map，键是分组条件，值是该组的元素列表
        Map<String, List<Integer>> grouped = numbers.stream()
                .collect(Collectors.groupingBy(x -> x % 2 == 0 ? "偶数" : "奇数"));
        // 输出分组结果：{偶数=[2, 4, 6, 8, 10], 奇数=[1, 3, 5, 7, 9]}
        System.out.println("4. 按奇偶分组: " + grouped);
        
        // partitioningBy - 分区操作
        // collect(Collectors.partitioningBy(...))根据布尔条件将元素分为两区
        // Predicate接口表示一个布尔值函数
        // x -> x > 5是一个Lambda表达式，表示将元素分为大于5和不大于5两区
        // 结果是一个Map，键是true和false，值是对应分区的元素列表
        Map<Boolean, List<Integer>> partitioned = numbers.stream()
                .collect(Collectors.partitioningBy(x -> x > 5));
        // 输出分区结果：{false=[1, 2, 3, 4, 5], true=[6, 7, 8, 9, 10]}
        System.out.println("5. 按是否大于5分区: " + partitioned);
        
        System.out.println();
    }
    
    /**
     * 测试并行流
     * 并行流可以利用多核处理器并行处理数据，提高处理效率
     * 并行流将数据分成多个部分并行处理，最后合并结果
     * 适用于CPU密集型操作，但要注意线程安全问题
     */
    public static void testParallelStream() {
        System.out.println("=== 并行流测试 ===");
        
        // 创建一个包含1到10的整数列表
        // 这是一个有序的、无重复的整数序列
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        
        // 顺序流处理
        // 记录开始时间
        // System.currentTimeMillis()返回当前时间毫秒数
        long startTime = System.currentTimeMillis();
        // 使用stream()创建顺序流
        // 顺序流按照元素在源中的顺序依次处理
        // map(x -> x * x)将每个元素平方
        // filter(x -> x > 10)过滤出平方大于10的元素
        // collect(Collectors.toList())将结果收集到List中
        List<Integer> result1 = numbers.stream()
                .map(x -> x * x)
                .filter(x -> x > 10)
                .collect(Collectors.toList());
        // 计算顺序流处理耗时
        // 计算时间差以测量执行时间
        long sequentialTime = System.currentTimeMillis() - startTime;
        
        // 并行流处理
        // 记录开始时间
        startTime = System.currentTimeMillis();
        // 使用parallelStream()创建并行流
        // 并行流会将数据分成多个部分并行处理，最后合并结果
        // 对于CPU密集型操作，并行流可以显著提高性能
        // ForkJoinPool是并行流默认使用的线程池
        List<Integer> result2 = numbers.parallelStream()
                .map(x -> x * x)
                .filter(x -> x > 10)
                .collect(Collectors.toList());
        // 计算并行流处理耗时
        long parallelTime = System.currentTimeMillis() - startTime;
        
        // 输出顺序流结果：[16, 25, 36, 49, 64, 81, 100]
        // 顺序流和并行流的结果应该是一样的
        System.out.println("1. 顺序流结果: " + result1);
        // 输出并行流结果：[16, 25, 36, 49, 64, 81, 100]
        System.out.println("2. 并行流结果: " + result2);
        // 输出顺序流耗时（毫秒）
        // 由于数据量较小，顺序流可能更快，因为并行处理有额外开销
        System.out.println("3. 顺序流耗时: " + sequentialTime + "ms");
        // 输出并行流耗时（毫秒）
        System.out.println("4. 并行流耗时: " + parallelTime + "ms");
        
        System.out.println();
    }
}